package us.gaaoc.framework.util.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;

import javax.faces.context.FacesContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import us.gaaoc.framework.dao.persistenceLogic.BaseDataAccessor;
import us.gaaoc.framework.model.CaseRecord;
import us.gaaoc.framework.model.DocumentBinaries;
import us.gaaoc.framework.model.Documents;
import us.gaaoc.framework.model.Person;

/**
 * @author Sukahda Tapshalkar
 * Aug 20, 2007
 * 
 * @modifier Brandon Denney
 * @modified 2008/07/21
 * @changes Added the ability to read binaries from the file system.
 *
 */
@SuppressWarnings("deprecation")
public class FileUtility extends BaseDataAccessor{
	
	/* **************** *
	 * Global Variables *
	 * **************** */
	/** Default Serial Version UID */
	private static final long serialVersionUID = 1L;
	/** The base path where the documents will be stored. */
	//private static final String baseFilePath ="/srv/documents/";
	
	
	
	private static final String baseFilePath ="C:" + File.separator + "documents" + File.separator ;

	
	private static Logger LOG = Logger.getLogger(FileUtility.class);
	
	public static String getDataFilePath() {
		return dataFilePath;
	}
	//private static final String dataFilePath = 	baseFilePath + "/data_files/";
	
	private static final String dataFilePath = 	baseFilePath + File.separator + "data_files" + File.separator;

	
	public File getContentAsFileHandle(Documents doc)
	{
		String path = this.getModelFacade()
				.findDocumentBinaryByUuid(doc.getUuid()).getPath();
			
			File pdf = new File(path);
			return pdf;
	}
	
	/* ****** *
	 * Getter *
	 * ****** */
	/**
	 * Getter for the baseFilePath String.
	 * 
	 * @return the base file path where the document structure will be saved.
	 */
	public static String getBaseFilePath() {
		return baseFilePath;
	}
	
	/**
	 * Default Constructor.	
	 */
	public FileUtility() {}
	
	/**
	 * This method is meant to replace the deprecated getContent method of
	 * the documents class. Given a document object, it returns the content
	 * as a byte array.
	 * 
	 * @param doc the document to get the byte array of.
	 * @return the byte array of the passed document.
	 */
	public byte[] getContent(Documents doc) {
		
		byte[] docContent = null;
		
		String path = this.getModelFacade()
			.findDocumentBinaryByUuid(doc.getUuid()).getPath();
		
		File pdf = new File(path);
	
		try {
			InputStream iStream = new FileInputStream(pdf);
			docContent = FileUtility.read2list(iStream);
			
		} catch (FileNotFoundException e) {
			// don't print exception. Too much output for no information.
			LOG.info("File Not Found for PDF " + doc.getUuid() + " " + path);		
		} catch (IOException e) {
			LOG.error("Unknown error reading PDF " + doc.getUuid() + " " + path, e);			
		} 
		
		return docContent;
	}
	
	/**
	 * This method is meant to replace the deprecated setContent method of the
	 * documents class.  Given a byte array, it saves the file into the file
	 * system.
	 * 
	 * @param doc the document to be saved.
	 * @param binary the pdf binary to be saved in the file system.
	 */
	public void setContent(Documents doc,Person p, byte[] binary) {
		
		int newVersionNumber = this.getModelFacade().getCurrentVersion
			(doc.getUuid()) + 1;

		Calendar c = new GregorianCalendar();
		c.setTime(doc.getReceivedDateTime());
		
		//Set the file structure.
		String path = FileUtility.getBaseFilePath() +
				doc.getCaseRecord().getCourtUuid() + "/" + 
				Integer.toString(c.get(Calendar.YEAR)) + "/" +
				Integer.toString(c.get(Calendar.MONTH)) + "/";
		
		File newPath = new File(path);
		newPath.mkdirs();
		
		//Create the file.
		File newFileVersion = new File(
				path + doc.getUuid() + "_V" + newVersionNumber);
		
		try {
			//Write the byte array to the file, if the array is not null.
			newFileVersion.createNewFile();
			FileOutputStream outStream = new FileOutputStream(newFileVersion);
			if (binary != null) {
				outStream.write(binary);
				outStream.flush();

				//Insert the document into the documentBinaries table.
				DocumentBinaries entry = new DocumentBinaries();
				
				entry.setUuid(UUID.randomUUID().toString());
				entry.setMetaDataUuid(doc);
				entry.setVersion(newVersionNumber);
				entry.setPath(newFileVersion.getPath());
				entry.setPerson(p);

				getModelFacade().makePersistent(entry);
			}
			outStream.close();
		} catch (FileNotFoundException fnf) {
			fnf.printStackTrace();
		} catch (IOException io) {
			io.printStackTrace();
		}
	}
	
	public static File createNewFile(String prefix, String suffix,
			File fileDirectory, byte[] fileBytes) {
		File newFile = null;
		FileOutputStream fw = null;
		try {
			newFile = File.createTempFile(prefix, suffix,fileDirectory);
			fw = new FileOutputStream(newFile);
			fw.write(fileBytes);
			fw.flush();
			fw.close();
		} catch (IOException e) {
			LOG.debug(e.toString());
			e.printStackTrace();
		} 		
		return newFile;
	}
	
	// Return the contents of the file in a byte array.
	public static byte[] getBytesFromFile(File file) throws IOException {
		InputStream is = new FileInputStream(file);

		// Get the size of the file
		long length = file.length();

		// You cannot create an array using a long type.
		// It needs to be an int type.
		// Before converting to an int type, check
		// to ensure that file is not larger than Integer.MAX_VALUE.
		if (length > Integer.MAX_VALUE) {
			// File is too large
		}

		// Create the byte array to hold the data
		byte[] bytes = new byte[(int) length];

		// Read in the bytes
		int offset = 0;
		int numRead = 0;
		while (offset < bytes.length
				&& (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
			offset += numRead;
		}

		// Ensure all the bytes have been read in
		if (offset < bytes.length) {
			LOG.error("Could not completely read file "
					+ file.getName());
			throw new IOException("Could not completely read file "
					+ file.getName());
		}

		// Close the input stream and return bytes
		is.close();
		return bytes;
	}

	/**
	 * This method converts the File to InputStram object
	 * @param newFilePassedIn
	 * @return
	 */
	public static FileInputStream convertFileToInputStream(File newFilePassedIn) {
		File file = newFilePassedIn;
		// Open PDF as document
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			LOG.error(e);
			e.printStackTrace();
		}
		
		return fis;		
	}

	/**
	 * This method pulls the content from the PDF file to view
	 * @param response
	 * @param fileName
	 * @param content
	 * @throws IOException
	 */
	public static String streamReportToBrowser(HttpServletResponse response,
			String fileName, byte[] content) throws IOException {

		if(content == null)
		{
			LOG.info("PDF 404 Error on file " + fileName);
			return "pdfNotFound";
		}
		
		response.setContentType("application/x-download");
		response.setHeader("Content-Disposition", "attachment;filename= "
				+ URLEncoder.encode(fileName,"UTF-8") + ".pdf");

		
		try {
					
			
			InputStream in = new ByteArrayInputStream(content);
			ServletOutputStream saos = response.getOutputStream();

			int inputdata;
			while ((inputdata = in.read()) > -1) {
				saos.write(inputdata);
			}
			saos.close();
			in.close();
			
		} catch (IOException ioEx) {
			LOG.error(ioEx);
			throw new IOException(ioEx.getMessage());
		}finally{
			//context.responseComplete();
		}
		return "stay";
	}
	
	/**
	 * This method pulls the content from the PDF file to view
	 * @param response
	 * @param fileName
	 * @param content
	 * @param formatCode An integer to represent the format of the download.
	 * <ol>
	 * 	<li> 1 = PDF - a portable download </li>
	 *  <li> 2 = RTF - an editable download  </li>
	 *  <li> 3 = PNG - an image based download </li>
	 *  </ol>
	 * @throws IOException
	 */
	public static void streamReportToBrowser(HttpServletResponse response,
			String fileName, byte[] content, int formatCode) throws IOException {

		response.setContentType("application/x-download");
		// PDF is the most common format expected to be used
		if (formatCode == 1) {
			response.setHeader("Content-Disposition", "attachment;filename= "
					+ fileName + ".pdf");
		// RTF format type
		} else if (formatCode == 2) {
			response.setHeader("Content-Disposition", "attachment;filename= "
					+ fileName + ".rtf");
		// PNG format type
		} else if (formatCode == 3) {
			response.setHeader("Content-Disposition", "attachment;filename= "
					+ fileName + ".png");
		// Default to PDF if unknown option passed in
		} else {
			LOG.debug("Unknown parameter passed in to streamReportToBrowser " +
					"method.  Defaulting to PDF.");
			response.setHeader("Content-Disposition", "attachment;filename= "
					+ fileName + ".pdf");
		}
		

		try {
			InputStream in = new ByteArrayInputStream(content);
			ServletOutputStream saos = response.getOutputStream();

			int inputdata;
			while ((inputdata = in.read()) > -1) {
				saos.write(inputdata);
			}
			saos.close();
			in.close();

		} catch (IOException ioEx) {
			LOG.error(ioEx);
			throw new IOException(ioEx.getMessage());
		}
	}
	
	/**

	   *  Reads a file storing intermediate data into a list. Fast method.

	   *  @param file the file to be read

	   *  @return a file data

	   */

	   public static byte[] read2list(String file) throws IOException {

	      InputStream in = null;

	      byte[] buf             = null; // output buffer

	      int    bufLen          = 20000*1024;

	      try{

	         in = new BufferedInputStream(new FileInputStream(file));

	         buf = new byte[bufLen];

	         byte[] tmp = null;

	         int len    = 0;

	         List data  = new ArrayList(24); // keeps peaces of data

	         while((len = in.read(buf,0,bufLen)) != -1){

	            tmp = new byte[len];

	            System.arraycopy(buf,0,tmp,0,len); // still need to do copy

	            data.add(tmp);

	         }

	         /*

	            This part os optional. This method could return a List data

	            for further processing, etc.

	         */

	         len = 0;

	         if (data.size() == 1) return (byte[]) data.get(0);

	         for (int i=0;i<data.size();i++) len += ((byte[]) data.get(i)).length;

	         buf = new byte[len]; // final output buffer

	         len = 0;

	         for (int i=0;i<data.size();i++){ // fill with data

	           tmp = (byte[]) data.get(i);

	            System.arraycopy(tmp,0,buf,len,tmp.length);

	            len += tmp.length;

	         }

	      }finally{

	         if (in != null) try{ in.close();}catch (Exception e){}

	      }

	      return buf;

	   }	
	   
	   public static byte[] read2list(InputStream  is) throws IOException {

	      InputStream in = null;

	      byte[] buf             = null; // output buffer

	      int    bufLen          = 20000*1024;

	      try{

	         in = new BufferedInputStream(is);

	         buf = new byte[bufLen];

	         byte[] tmp = null;

	         int len    = 0;

	         List data  = new ArrayList(24); // keeps peaces of data

	         while((len = in.read(buf,0,bufLen)) != -1){

	            tmp = new byte[len];

	            System.arraycopy(buf,0,tmp,0,len); // still need to do copy

	            data.add(tmp);

	         }

	         /*

	            This part os optional. This method could return a List data

	            for further processing, etc.

	         */

	         len = 0;

	         if (data.size() == 1) return (byte[]) data.get(0);

	         for (int i=0;i<data.size();i++) len += ((byte[]) data.get(i)).length;

	         buf = new byte[len]; // final output buffer

	         len = 0;

	         for (int i=0;i<data.size();i++){ // fill with data

	           tmp = (byte[]) data.get(i);

	            System.arraycopy(tmp,0,buf,len,tmp.length);

	            len += tmp.length;

	         }

	      }finally{

	         if (in != null) try{ in.close();}catch (Exception e){}

	      }

	      return buf;

	   }
	
	   
	/**
	 * Reads the specified file into a byte array.
	 */
	   
	public static byte[] readFileInByteArray(InputStream document)
			throws IOException {
		return read2list(document);
		// Somewhere to put the bytes
		/*
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		int inputdata;// a number to compared to see if more bytes are
						// available
		try {
			while ((inputdata = document.read()) > -1) {
				// While the inputput data still contains bytes....
				// write them to the output stream
				baos.write(inputdata);
			}
			// return the output stream as a byte array
			return baos.toByteArray();
		} finally {
			document.close();
		}
		*/
		
	}

	/*
	public static FileDescriptor CopyFile(FileInputStream src,
			FileOutputStream out) throws IOException {
		int ch;
		try {
			while ((ch = src.read()) != -1) {
				out.write(ch);
			}
			src.close();
			out.flush();
			out.close();
		} catch (Exception e) {
			System.out.println("error " + e);
		}
		return out.getFD();
	*/
	
	public static FileDescriptor CopyFile(FileInputStream sourceFile, FileOutputStream out) throws IOException 
	{		 
		 FileChannel source = null;
		 FileChannel destination = null;
		 try {
		  source = sourceFile.getChannel();
		  destination = out.getChannel();
		  destination.transferFrom(source, 0, source.size());
		 }
		 finally {
		  if(source != null) {
		   source.close();
		  }
		  if(destination != null) {
		   destination.close();
		  }
		}
		 return out.getFD();
	}

	

	public static void SaveByteArray(byte[] bytes, String name)
			throws IOException {
		BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(name)));
		fos.write(bytes);
		fos.flush();
		fos.close();
	}

	/**
	 * This method is designed to take any document with any extension and stream it to the browser.
	 * This is supposed to replace all the other variations of this method.
	 * 
	 * @param response
	 * @param fileName
	 * @param docContent
	 * @param docExtension
	 * @throws IOException
	 */
	public static void streamReportToBrowser(HttpServletResponse response,
			String fileName, byte[] docContent, String docExtension)
			throws IOException {

		response.setContentType("application/x-download");
		response.setHeader("Content-Disposition", "attachment;filename= "
				+ fileName + docExtension);

		try {
			BufferedOutputStream saos = new BufferedOutputStream(response.getOutputStream());
			
			saos.write(docContent);
			
			saos.close();

		} catch (IOException ioEx) {
			LOG.error(ioEx);
			throw new IOException(ioEx.getMessage());
		}
	}
}
